﻿/************************************************************************
 * AIDE - Automata Identification Engine
 * Copyright (C) 2012-2013 Ali Khalili (khalili_DOT_ir_@SIGN_gmail_DOT_com)
 * **********************************************************************
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License (LGPL) published 
 * by the Free Software Foundation; either version 2.1 of the License, or (at your
 * option) any later version. This program is distributed without any
 * warranty. See the GNU Lesser General Public License for more details. 
 * ***********************************************************************
 * File         :     AIDE\OnlineAlgo\DFAs\L_Star.cs
 * Description  :     Implementation of Angluin L* algorithm for learning DFAs
 * Change log   :     -19 Sep 2014: Fix several buges!
 * 					  -25 Feb 2013: using Interface Oracle instead of class oracle
 *                    -22 Feb 2013: a little polishment
 *                    -21 Feb 2013: remove string
 *                    -Dec 2012: Initial Version
 * To Do        :
 ************************************************************************/
using System;
using System.Collections.Generic;
using SMLib.Automata.Base;
using SMLib.Automata.DFA;
using AIDE.Oracles.DFAs;
using AIDE.Base;

/// <summary>
/// Online learning algorithms for regular languages
/// </summary>
namespace AIDE.OnlineAlgo.DFAs
{
	/// <summary>
	/// Angluin DFA Learning Algorithm L*
	/// <remarks>Based on "Angluin, D.: Learning regular sets from queries and counterexamples (1987)"</remarks>
	/// </summary>
	public class L_Star
	{
		protected IOracle_MAT_DFA myOracle;
		protected int phase = 0;

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="oracle">the oracle to be used by learner</param>
		public L_Star (IOracle_MAT_DFA oracle)
		{
			myOracle = oracle;
			
			//* the output format
			string oFormat = LibSetting.ConfOf ("AIDE.LStar.ExportTableFormat");
			switch (oFormat) {
			case "XML":
				exportFormat = ExportOptions.XML;
				break;
			case "HTML":
				exportFormat = ExportOptions.HTML;
				break;
			case "CSV":
				exportFormat = ExportOptions.CSV;
				break;
			case "NONE":
				exportFormat = ExportOptions.NONE;
				break;
			default:
				exportFormat = ExportOptions.NONE;
				Console.Write ("\"AIDE.LStar.ExportTableFormat\" is wrong or not defined!\n");
				break;
			}
		}

		/// <summary>
		/// The Td is not implemented as a construct and it is a set of rows spread among SD and Sd_Sigmas
		/// </summary>
        #region ObservationTable

		/// SD and ED be the non-empty finite sets of finite strings over Sigma. 
		/// SD is a prefix-closed set 
		/// ED is a suffix-closed set of strings.
		protected List<TD_Row> Sd;
		protected List<TD_Row> Sd_Sigma;
		protected List<Word> Ed;


        #region observation table row
		protected class TD_Row
		{
			public TD_Row parent;

			public void SetParent (TD_Row parent)
			{
				this.parent = parent;
			}

			public Word p;
			public List<bool> Ed_Row;
			public int generatedState = -1;

			public TD_Row ()
			{
				p = new Word ();
				Ed_Row = new List<bool> ();
			}

			public TD_Row (TD_Row r, Word op = null)
			{
				if (op == null)
					p = new Word (r.p);
				else
					p = new Word (op);

				Ed_Row = new List<bool> ();
				for (int i = 0; i < r.Ed_Row.Count; i++)
					Ed_Row.Add (false);
			}

			public bool HasSameRow (TD_Row t)
			{
				if (t.Ed_Row.Count != Ed_Row.Count)
					return false;
				for (int i = 0; i < Ed_Row.Count; i++)
					if (FirstDifference (t) > -1)
						return false;
				return true;
			}

			/// <summary>
			/// Assume that the lenght of two rows are the same
			/// </summary>
			/// <param name="anotherRow"></param>
			/// <returns></returns>
			public int FirstDifference (TD_Row t)
			{
				for (int i = 0; i < Ed_Row.Count; i++)
					if (Ed_Row [i] != t.Ed_Row [i])
						return i;
				return -1;
			}

			public override string ToString ()
			{
				string st = "";
				for (int i = 0; i < Ed_Row.Count; i++)
					if (Ed_Row [i])
						st += "1";
					else
						st += "0";
				return p.ToString () + st;
			}
		}
        #endregion



		/// <summary>
		/// Initialize the observation table
		/// <para>
		/// Let TD be a finite function that maps (SD union SD.Sigma)* ED to {0; 1}
		/// An observation table (SD,ED,TD) can be visualized as a two-dimensional array with rows labeled by the elements of
		/// SD union SD.Sigma and columns labeled by the elements of ED, with the entry for a row input in 
		/// SD union SD.Sigma and a column e 2 ED equals to TD(input; e).
		/// </para>
		/// </summary>
		protected void InitializeObservationTable ()
		{
			bool testRes = false;

			//initialize
			Sd = new List<TD_Row> ();
			Sd_Sigma = new List<TD_Row> ();
			Ed = new List<Word> ();

			//Ed={""}
			Word eps = new Word ();
			Ed.Add (eps);

			//Sd={""}
			TD_Row r = new TD_Row ();
			Sd.Add (r);

			bool epIsOk = myOracle.MembershipQuery (eps);
			r.Ed_Row.Add (epIsOk);

			//add all words of Sigma into Sd_Sigma
			foreach (ISymbol k in myOracle.inputAlphabet.AllSymbols()) {
				TD_Row r2 = new TD_Row ();
				r2.SetParent (r);
				r2.p.AddSymbol (k);
				Sd_Sigma.Add (r2);
				//test that the system accept the Word myOracle not
				testRes = myOracle.MembershipQuery (r2.p);
				r2.Ed_Row.Add (testRes);
			}
		}


		/// <summary>
		/// To add another suffix, we need to add a column into the observation table.
		/// By default, this suffix leads to a non-accepting state for all prefixes.
		/// Afterward, we need to update it by trying to call membership query.
		/// </summary>
		protected void AddColumn ()
		{
			foreach (TD_Row t in Sd) {
				t.Ed_Row.Add (false);
			}
			foreach (TD_Row t in Sd_Sigma) {
				t.Ed_Row.Add (false);
			}
		}

		/// <summary>
		/// Check that the observation table is closed myOracle not
		/// </summary>
		/// <returns>
		/// -1: the observation table is Closed
		/// i : the first row number of Sd.Sigma that makes the table un-closed
		/// </returns>
		protected int isClosed ()
		{
			for (int i = 0; i < Sd_Sigma.Count; i++) {
				TD_Row t = Sd_Sigma [i];
				
				bool found = false;
				foreach (TD_Row r in Sd) {
					if (r.HasSameRow (t)) {
						found = true;
						break;
					}
				}
				if (!found) {
					//there is no equivalent row in Sd corresponding to this Sd_Sigma row
					return i;
				}
			}
			//the table is closed
			return -1;
		}

		bool isConsistant ()
		{
			throw new Exception ("Not implemented. Consistency is checked inline!");
		}

		/// <summary>
		/// Build the conjecture automata based on observation table
		/// </summary>
		/// <returns>
		/// the generated automata
		/// </returns>
		protected DFA BuildConjecture ()
		{
			DFA conj = new DFA (myOracle.inputAlphabet);
			DFA.DFA_State currentState, nextState;
			DFA.DFA_Transition tr;
			int currentRowIndex = 0;
			Word p;
			TD_Row currentRow;

			currentState = new DFA.DFA_State ((Sd [0].Ed_Row [0] ? "T" : "F") + "0");//epsilon
			currentState.isAccepting = Sd [0].Ed_Row [0];//instead of true
			conj.AddState (currentState);//add initial state
			currentRow = Sd [0];


			List<Word> dfaStates = new List<Word> ();
			Word eps = new Word ();
			//eps.AddStep("");
			dfaStates.Add (eps);

			List<TD_Row> statedRow = new List<TD_Row> ();
			statedRow.Add (Sd [0]);

			//generate states breast first order
			//bool anyStateGenerated;
			for (currentRowIndex = 0; currentRowIndex < statedRow.Count; currentRowIndex++) {
				//anyStateGenerated = false;
				currentRow = statedRow [currentRowIndex];
				currentState = (DFA.DFA_State)conj [currentRowIndex];

				foreach (ISymbol s in myOracle.inputAlphabet.AllSymbols()) {
					p = new Word (currentRow.p);
					p.AddSymbol (s);
					bool nextRowFound = false;
					bool stateShoudbeGenerated = true;

					//find next row (counterexample) in S:
					for (int i = 1; i < Sd.Count; i++) {
						if (Sd [i].p.Equals (p)) {
							//Found!
							nextRowFound = true;

							//find that this row exists in the generated states?
							for (int j = 0; j < statedRow.Count; j++) {
								TD_Row r = statedRow [j];
								if (r.HasSameRow (Sd [i])) {
									//generate transition to this state from current state
									tr = new DFA.DFA_Transition (s);
									tr.SetDestination ((DFA.DFA_State)conj [j]);
									currentState.Transitions.Add (tr);
									stateShoudbeGenerated = false;
									break;
								}
							}
							//if (nextRowFound) break;

							//a new state shoud be generated!
							if (stateShoudbeGenerated) {
								statedRow.Add (Sd [i]);
								//anyStateGenerated = true;
								nextState = new DFA.DFA_State (
									(Sd [i].Ed_Row [0] ? "T" : "F") + conj.Size.ToString ());
								nextState.isAccepting = Sd [i].Ed_Row [0];//is Final state?
								conj.AddState (nextState);
								tr = new DFA.DFA_Transition (s);
								tr.SetDestination (nextState);
								currentState.Transitions.Add (tr);
							}
							break;
						}
					}
					if (nextRowFound)
						continue;

					//Not found in Sd? No problem. So find next row (counterexample) in Sd_Sigma...
					for (int i = 0; i < Sd_Sigma.Count; i++) {
						if (Sd_Sigma [i].p.Equals (p)) {
							stateShoudbeGenerated = true;
							//find that this row exists in the generated states?
							for (int j = 0; j < statedRow.Count; j++) {
								TD_Row r = statedRow [j];
								if (r.HasSameRow (Sd_Sigma [i])) {
									//generate transition to this state from current state
									tr = new DFA.DFA_Transition (s);
									tr.SetDestination ((DFA.DFA_State)conj.GetStates () [j]);
									currentState.Transitions.Add (tr);
									stateShoudbeGenerated = false;
									break;
								}
							}
							if (stateShoudbeGenerated) {
								//Found in Sd_Sigma! Now, find a row in Sd with similar TD_row

								for (int k = 1; k < Sd.Count; k++) {
									TD_Row r = Sd [k];
									if (r.HasSameRow (Sd_Sigma [i])) {
										statedRow.Add (Sd [k]);
										//anyStateGenerated = true;
										nextState = new DFA.DFA_State ((Sd [k].Ed_Row [0] ? "T" : "F") + conj.Size.ToString ());
										nextState.isAccepting = Sd [k].Ed_Row [0];
										
										conj.AddState (nextState);
										tr = new DFA.DFA_Transition (s);
										tr.SetDestination (nextState);
										currentState.Transitions.Add (tr);
										break;
									}
								}
							}
							break;
						}
					}
				}
			}
			
			return conj;
		}

		
		
        #endregion
		
		#region -=[export obsevation table]=-
		enum ExportOptions
		{
			XML,
			CSV,
			HTML,
			NONE
		};
		ExportOptions exportFormat;
		
		/// <summary>
		/// Prints the table into XML format (for debbugging use) into 
		/// "Table+phase.xml" in the working directory
		/// </summary>
		protected void ExportObservationTable ()
		{
			//exportFormat = ExportOptions.HTML;
			if (exportFormat == ExportOptions.NONE)
				return;
			else if (exportFormat == ExportOptions.XML) {
				System.Text.StringBuilder sb = new System.Text.StringBuilder ();
				sb.AppendLine ("<table phase=\"" + phase + "\">");
				for (int i=0; i<(this.Sd.Count+this.Sd_Sigma.Count); i++) {
					sb.Append ("<row accessSequence=\"");
					if (i < Sd.Count)
						sb.AppendLine (Sd [i].p + "\" of=\"Sd\">");
					else
						sb.AppendLine (Sd_Sigma [i - Sd.Count].p + "\" of=\"Sd_Sigma\">");
				

					for (int j=0; j<Ed.Count; j++) {
						sb.AppendLine ("<cell column=\"" + j + "\" header=\"" + Ed [j].ToString () + "\">");
						if (i < Sd.Count) {
							string st = Sd [i].Ed_Row [j].ToString ();
							sb.AppendLine (st.ToString ());
						} else {
							string st = Sd_Sigma [i - Sd.Count].Ed_Row [j].ToString ();
							sb.AppendLine (st.ToString ());
						}
						sb.AppendLine ("</cell>");					
					}
					sb.AppendLine ("</row>");
				}
				sb.AppendLine ("</table>");
				System.IO.File.WriteAllText (LibSetting.WorkingPath + "Table" + this.phase + ".xml",
			                             sb.ToString ());
			} else if (exportFormat == ExportOptions.HTML) {
				System.Text.StringBuilder sb = new System.Text.StringBuilder ();
				sb.AppendLine ("<html><body>Observation table (phase " + phase + ")<table border=\"1\">");
				sb.Append ("<tr><th><b>Access Sequence</th>");				
				for (int j=0; j<Ed.Count; j++)
					sb.Append ("<th>" + Ed [j] + "</th>");
				sb.Append ("</tr>");
				
				for (int i=0; i<(this.Sd.Count+this.Sd_Sigma.Count); i++) {
					if (i < Sd.Count) 
						sb.Append ("<tr><td bgcolor=\"#BEE6B8\">");
					else
						sb.Append ("<tr><td bgcolor=\"#EDD5A8\">");
					
					if (i < Sd.Count)
						sb.Append (Sd [i].p + "</td>");
					else
						sb.Append (Sd_Sigma [i - Sd.Count].p + "</td>");
				

					for (int j=0; j<Ed.Count; j++) {
						sb.Append ("  <td>");
						if (i < Sd.Count) {
							string st = Sd [i].Ed_Row [j].ToString ();
							sb.Append (st.ToString ());
						} else {
							string st = Sd_Sigma [i - Sd.Count].Ed_Row [j].ToString ();
							sb.Append (st.ToString ());
						}
						sb.AppendLine ("</td>");					
					}
					sb.AppendLine ("</tr>");
				}
				sb.AppendLine ("</body></html>");
				System.IO.File.WriteAllText (LibSetting.WorkingPath + "Table" + this.phase + ".html",
			                             sb.ToString ());
			} else if (exportFormat == ExportOptions.CSV) {
				System.Text.StringBuilder sb = new System.Text.StringBuilder ();
				sb.Append ("Access Sequence");				
				for (int j=0; j<Ed.Count; j++)
					sb.Append (", " + Ed [j]);
				sb.AppendLine ();
				
				for (int i=0; i<(this.Sd.Count+this.Sd_Sigma.Count); i++) {
					if (i < Sd.Count)
						sb.Append (Sd [i].p + ", ");
					else
						sb.Append (Sd_Sigma [i - Sd.Count].p + ", ");
				
					for (int j=0; j<Ed.Count; j++) {
						if (i < Sd.Count) {
							string st = Sd [i].Ed_Row [j].ToString ();
							sb.Append (st.ToString ());
						} else {
							string st = Sd_Sigma [i - Sd.Count].Ed_Row [j].ToString ();
							sb.Append (st.ToString ());
						}
						sb.Append (",");					
					}
					sb.AppendLine ("");
				}
				System.IO.File.WriteAllText (LibSetting.WorkingPath + "Table" + this.phase + ".csv",
			                             sb.ToString ());
			}
		}

		#endregion

		/// <summary>
		/// Update a row in observation table
		/// </summary>
		/// <param name="tdr"></param>
		protected void UpdateTD_Row (TD_Row tdr)
		{
			for (int k = 0; k < tdr.Ed_Row.Count; k++) {
				Word myWord = new Word (tdr.p, Ed [k]);

				bool acceptable = myOracle.MembershipQuery (myWord);
				tdr.Ed_Row [k] = acceptable;
			}
		}

		/// <summary>
		/// Index of path
		/// </summary>
		/// <param name="lst"></param>
		/// <param name="p"></param>
		/// <returns></returns>
		protected int indexOfPath (List<TD_Row> lst, Word p)
		{
			for (int i = 0; i < lst.Count; i++) {
				if (lst [i].p.Equals (p))
					return i;
			}
			return -1;
		}

		
		/// <summary>
		/// Start learning algorithm
		/// <remarks>Based on Algorithm.1 (page 46) of Shahbaz PhD Thesis</remarks>
		/// </summary>
		public virtual DFA Learn ()
		{
			//int lastConjSize = -1;
			//Sigma = referenceAutomata.Sigma;
			DFA conj;
            

			//initialize the observation table (SD;ED; TD) with the sets
			//SD = ED = {epsilon}; SD.Sigma = {Epsilon.i} for all i in Sigma;
			//ask the membership queries from (SD;ED; TD) ;
			//update (SD;ED; TD) with the results of the queries ;
			InitializeObservationTable ();
			
			
			//repeat until oracle replies yes to the conjecture MD 
			while (true) {
				#region -=[ Make the table closed and consistent ]=-
				bool c1 = true, c2 = true;
				int effort = 0;
				//while (SD;ED; TD) is not closed myOracle not consistent do
				while (true) {
                    #region Consistency

					//if (SD;ED; TD) is not consistent then
					//   find input,anotherRow in SD; e in ED; i in Sigma; such that input ~ED anotherRow
					//      but TD(input  i; e) != TD(anotherRow  i; e)
					//   add i .e to ED
					//   ask membership queries for the extended table
					//end 18
					//if (!c2)
					//{

					//if the table was not consistent and we made it consistent and the table still is closed, no need
					//ti check consistency again!
					//if (c1 && (!c2)) break;
					c2 = true;

					bool EdImproved = false;
					for (int itr1 = 0; itr1 < Sd.Count; itr1++) {
						for (int itr2 = 0; itr2 < Sd.Count; itr2++) {
							
							if (itr1 >= itr2)
								continue;
									
							if (Sd [itr1].HasSameRow (Sd [itr2])) {
								TD_Row s = Sd [itr1];
								TD_Row t = Sd [itr2];
								
								//Find T(input,i,e):  for each i in Sigma
								foreach (ISymbol i in myOracle.inputAlphabet.AllSymbols()) {
									TD_Row si = null, ti = null;

									//find input.i and anotherRow.i [using parent pointer]
									foreach (TD_Row td in Sd) {
										if (td.p.IsLambda ())
											continue;
											
										if (td.parent == s &&
                                            (td.p [td.p.Length - 1].Equals (i) /*|| td.p.IsLambda ()*/))
											si = td;
										else if (td.parent == t &&
                                            (td.p [td.p.Length - 1].Equals (i) /*|| td.p.IsLambda ()*/))
											ti = td;
										if (ti != null && si != null)
											break;
									}

									foreach (TD_Row td in Sd_Sigma) {
										if (td.p.IsLambda ())
											continue;
											
										if (td.parent == s &&
                                            (td.p [td.p.Length - 1].Equals (i) /*|| td.p.IsLambda ()*/))
											si = td;
										else if (td.parent == t &&
                                            (td.p [td.p.Length - 1].Equals (i) /*|| td.p.IsLambda ()*/))
											ti = td;
										if (ti != null && si != null)
											break;
									}
																		
									
									//find e in Ed
									int diff = ti.FirstDifference (si);
									if (diff > -1) {
										c2 = false;
										//found e!...
										Word ie = new Word ();
										//add i
										ie.AddSymbol (i);
										//add e
										for (int y = 0; y < Ed[diff].Length; y++)
											ie.AddSymbol (Ed [diff].GetSymByIndex (y));

										//add i.e to Ed
										Ed.Add (ie);
										EdImproved = true;
										//update the row by asking membership queries for Sd
										foreach (TD_Row tmp in Sd) {
											Word myWord = new Word (tmp.p, ie);
											bool testRes = myOracle.MembershipQuery (myWord);
											tmp.Ed_Row.Add (testRes);
										}
										//update the row by asking membership queries for Sd_Sigma
										foreach (TD_Row tmp in Sd_Sigma) {
											Word myWord = new Word (tmp.p, ie);
											bool testRes = myOracle.MembershipQuery (myWord);
											tmp.Ed_Row.Add (testRes);
										}
										break;
									}
								}
							}
							if (EdImproved)
								break;
						}
						if (EdImproved)
							break;
					}
                    #endregion
					
                    #region Closedness

					int nc = isClosed ();
					c1 = (nc == -1);

					//if (SD;ED; TD) is not closed then 
					//    find anotherRow in SD.Sigma  such that anotherRow  ~(ED) input, for all input in SD
					//    move anotherRow to SD
					//    ask membership queries for the extended table
					//end
					if (!c1) {
						//remove from Sd.Sigma and add to Sd
						TD_Row t = Sd_Sigma [nc];
						Sd_Sigma.RemoveAt (nc);
						Sd.Add (t);

						//add anotherRow.a for each a in Sigma if they are not exist there
						foreach (ISymbol a in myOracle.inputAlphabet.AllSymbols()) {
							TD_Row nt = new TD_Row (t);
							nt.SetParent (t);
							nt.p.AddSymbol (a);

							//don'anotherRow add if there is already one
							if (indexOfPath (Sd_Sigma, nt.p) > -1)
								continue;

							Sd_Sigma.Add (nt);
							for (int k = 0; k < t.Ed_Row.Count; k++) {
								Word myWord = new Word (nt.p, Ed [k]);
								bool acceptable = myOracle.MembershipQuery (myWord);
								nt.Ed_Row [k] = acceptable;
							}
						}
					}
                    #endregion
					
					if (c1 && c2) {
						
						if (effort == 0) {
							ExportObservationTable ();
				
							throw new Exception ("It didn't change without any effort!");
						}
						
						break;
					} else
						effort++;
				}
				
				#endregion
				
				//ask the equivalence query for MD 
				//if oracle replies with a counterexample v then
				//   add all the prefixes of v to SD
				//   ask membership queries for the extended table
				//end

				conj = BuildConjecture ();				
				
				Word cex = myOracle.EquivalenceQuery (conj); //oracle knows the alphabet
				
				//* export table
				ExportObservationTable ();
				//* export automata
				SMLib.Interoperability.DOT.DotHelper_DFA.WriteToDot ("DFA_" + phase.ToString () + ".dot", conj);
				
				phase++;

				//is there any counterexample?
				if (cex == null) {					
					//No! We found the RIGHT model
					//return the conjecture!
					return conj;				
				} else {
					Console.WriteLine (phase + ": Counterexample: " + cex);	
				}
				
				#region -=[ Handle the counterexample ]=-
				bool somethingAdded = false;
				
				//yes! so: add all prefix of the counterexample into Sd
				for (int i = 0; i < cex.Length; i++) {
					Word pr = cex.Prefix (i + 1);
					
					//the prefix is already in the Sd
					if (indexOfPath (Sd, pr) > -1)
						continue;
					
					somethingAdded = true;
					TD_Row tdr;
					
					//if it was in Sd_Sigma, remove it??
					int indx = indexOfPath (Sd_Sigma, pr);
					//*THIS IS WRING!!!!
					//indx=-1;
					if (indx > -1) {
						
						tdr = Sd_Sigma [indx];
						Sd.Add (Sd_Sigma [indx]);
						Sd_Sigma.RemoveAt (indx);
						
					} else {						
						tdr = new TD_Row (Sd [0], pr);
						int indx2 = indexOfPath (Sd, pr.Prefix (pr.Length - 1));
						//if(indx2>-1)
						//{
						tdr.SetParent (Sd [indx2]);
						//}
						//else
						//{
						//	indx2 = indexOfPath (Sd_Sigma, pr.Prefix (pr.Length - 1));
						//	tdr.SetParent(Sd_Sigma[indx2]);
						//}
						//counterexample was not in Sd. Add it!
						//Sd_Sigma.Add (tdr);
						Sd.Add (tdr);
						UpdateTD_Row (tdr);
					
					}
					
					//* Bug fix (performance improvement): Do not add elements to the counterexample "itself"
					//if(i==cex.Length-1)
					//	break;
					   
						
					for (int j = 0; j < myOracle.inputAlphabet.AllSymbols().Count; j++) {

						Word pr2 = new Word (pr);
						pr2.AddSymbol (myOracle.inputAlphabet.AllSymbols () [j]);
						if (indexOfPath (Sd_Sigma, pr2) > -1)
							continue;
						TD_Row tdr2 = new TD_Row (Sd [0], pr2);						
						tdr2.SetParent (tdr);
						
						UpdateTD_Row (tdr2);
						Sd_Sigma.Add (tdr2);
					}
					
				}
				if (somethingAdded == false) {
					throw new Exception ("Something went wrong!");
				}
				#endregion
			}
		}
	}
}
